package it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.ai.decisiontreeai;


import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import it.unical.canonaco.rende.igpe.game.cbv.datastructure.RBTree;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.AbilityAttackAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.ActionCoordinateInfo;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.ActionMediator;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.ActionSpellInfo;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.AddCharacterInfo;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.CharacterAttackAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.EndTurnAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.MovementAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.actions.StartingPointAction;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.ai.AutomatedPlayerController;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.stats.AbilityStats;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.stats.AbilityValue;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.stats.StatsFactory;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.world.Chessboard;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.world.Map;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.world.SpawnCoordinate;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.pawn.Character;
import it.unical.canonaco.rende.igpe.game.cbv.logiclayer.engine.component.player.BattlePlayer;

public class DecisionTreeAI extends AutomatedPlayerController {
	
	
	private static float MISSING_HEALTH_RATIO = 0.5f;
	
	DecisionTree decisionTree;
	private Chessboard arena;
	private StatsFactory factory;

	private LinkedList<Character> aliveCharacters;
	private Character currentChar;
	private int currentCharIndex;

	private Character targetEnemy;
	private int chosenSpell;
	
	private Character woundedAlly;
	
	// 3 CHARS 
	DecisionNode healerRoot;
	DecisionNode warriorRoot;
	DecisionNode mageRoot;
	
	public DecisionTreeAI(StatsFactory factory, final BattlePlayer enemyPlayer) {
		super(enemyPlayer);
		this.factory = factory;
		currentCharIndex = 0;
		// HEALER
		DecisionAlgorithm youHurtALG = new DecisionAlgorithm() {
			
			// are u hurt, healer?
			@Override
			public boolean chooseNode() {
				System.out.println("ROOT");
				System.out.println("SEI FERITO?");
		
				if(currentChar.getStats().getCharacteristics().getHealth()
						/ currentChar.getStats().getMaxCharacteristics().getHealth() <= MISSING_HEALTH_RATIO) {
					return true;
				
				}
				return false;
			}
		};
		
		// You have skill points for healing yourself?
		DecisionAlgorithm spForHealALG = new DecisionAlgorithm() {
			
			@Override
			public boolean chooseNode() {
				
				System.out.println("HAI 4 SP PER HEAL?");
				if(player.getSkillPoints() >= 4) {
					return true;
					
				}
				return false;
			}
		};
		
		DecisionAlgorithm have1SPointALG = new DecisionAlgorithm() {
			
			@Override
			public boolean chooseNode() {
				
				System.out.println("HAI ALMENO 1 SP?");
				if(player.getSkillPoints() > 1) {
					return true;
					
				}
				return false;
			}
		};
		
		ActionNode healYourselfAN = new ActionNode() {
			
			@Override
			public void makeAction() {
				System.out.println(currentChar.getName());
				System.out.println(currentChar.getRow()+" "+currentChar.getColumn());
				ActionMediator actions = player.getActions();
				System.out.println("AI: L'HEALER "+currentChar.getName()+" CASTA HEAL SU STESSO");
				actions.clearInformations();
				actions.setAction(AbilityAttackAction.GUID);
				actions.addActionInfo(new ActionCoordinateInfo(currentChar.getRow(), currentChar.getColumn()));
				actions.addActionInfo(new ActionSpellInfo(ActionSpellInfo.SECOND_SPELL));
				actions.addActionInfo(new ActionCoordinateInfo(currentChar.getRow(), currentChar.getColumn()));
			}
		};
		
		// Search for the most wounded ally with Health ratio < 0.5
		DecisionAlgorithm someoneInjuredALG = new DecisionAlgorithm() {
			
			@Override
			public boolean chooseNode() {
				System.out.println("QUALCUNO E' FERITO?");
				
				// Reset previous wounded character
				woundedAlly = null;
				
				// Max ratio possible
				float minRatio = 1;
				
				// Look for the most wounded character (including the caster)
				for(int i = 0; i< aliveCharacters.size(); i++) {
					float allyRatio = aliveCharacters.get(i).getStats().getCharacteristics().getHealth()
							/ aliveCharacters.get(i).getStats().getMaxCharacteristics().getHealth();
					
					if(allyRatio <= minRatio) {
						minRatio = allyRatio;
						woundedAlly = aliveCharacters.get(i);
					}
				}
				
				if(minRatio <= MISSING_HEALTH_RATIO)
					return true;
				return false;
			}
		};
		
		// With lowest HP
		DecisionAlgorithm enemyInRangeALG = new DecisionAlgorithm() {
			
			@Override
			public boolean chooseNode() {

				System.out.println("IL NEMICO CON POCHI HP E' NEL RANGE?");
				
				
				float hp = Float.MAX_VALUE;
				targetEnemy = null;
				
				for(Character enemy : enemyPlayer.getCharacters()) {
					
					if(enemy.isAlive() && enemy.getStats().getCharacteristics().getHealth() < hp){
						targetEnemy = enemy;
						hp = enemy.getStats().getCharacteristics().getHealth();
					}
				}
					
				if(Character.isInRange(currentChar.getRow(), targetEnemy.getRow(),
												currentChar.getColumn(), targetEnemy.getColumn(), 
												currentChar.getStats().getCharacteristics().getAttackRange()))
						return true;

				return false;
			}
		};
		
		DecisionAlgorithm canCastSpellALG = new DecisionAlgorithm() {
			
			@Override
			public boolean chooseNode() {

				System.out.println("PUO' CASTARE UNA SPELL?");
				if(currentChar.isSpellCastable())
					return true;
				return false;
			}
		};
		
		ActionNode castHighestEnemySpell = new ActionNode() {
			//!TODO faccio il controllo solo
			// su attack range ma deve essere attack range + spell range
			// INOLTRE POTREI AGGIUNGERE UN MOVMENT ACTION PRIMA
			@Override
			public void makeAction() {
				
//				try {
//					Thread.sleep(1500);
//				} catch (InterruptedException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
				
				float sp = player.getSkillPoints();
				float maxSp = 1;
				int index = -1;
				
				for(AbilityStats ability : currentChar.getStats().getAbilities()){
					AbilityValue spellInfo = ability.getAbilityValue();
					float cost = spellInfo.getCost();
					if(cost <= sp && cost > maxSp && !spellInfo.isFriendly()){
						maxSp = cost;
						index = spellInfo.getId();
					}
					
				}
				
				ActionMediator actions = player.getActions();
				actions.clearInformations();
				
				// It means u have no spells for 1 sp and then u must cast a basic attack
				if(index == -1) {
					System.out.println(currentChar.getName()+" NON HA ABBASTANZA SP E CASTA BASIC ATTACK");
					actions.setAction(CharacterAttackAction.GUID);
					actions.addActionInfo(new ActionCoordinateInfo(currentChar.getRow(), currentChar.getColumn()));
					actions.addActionInfo(new ActionCoordinateInfo(targetEnemy.getRow(), targetEnemy.getColumn()));

				}
				else {
					System.out.println(currentChar.getName()+" CASTA LA PIU' ALTA SPELL DI ID "+index);
					
					actions.setAction(AbilityAttackAction.GUID);
					actions.addActionInfo(new ActionCoordinateInfo(currentChar.getRow(), currentChar.getColumn()));
					actions.addActionInfo(new ActionSpellInfo(index));
					actions.addActionInfo(new ActionCoordinateInfo(targetEnemy.getRow(), targetEnemy.getColumn()));
					System.out.println(targetEnemy.getRow()+" "+targetEnemy.getColumn()+" cioe champion di "+targetEnemy.getOwner().getNickname());
					System.out.println("DAFUQ");
				}
				
			}
		};
		
		DecisionAlgorithm canYouMoveCloserALG = new DecisionAlgorithm() {
			
			@Override
			public boolean chooseNode() {
				
				System.out.println("MI POSSO ANCORA MUOVERE?");
				if(!currentChar.isMoved())
				return true;
			return false;
			}
		};
		
		DecisionAlgorithm haveAnotherCharALG = new DecisionAlgorithm() {
			
			@Override
			public boolean chooseNode() {
				
				System.out.println("C'E' UN ALTRO PERSONAGGIO DA UTILIZZARE?");

				currentCharIndex = ++currentCharIndex % aliveCharacters.size();

				if(currentCharIndex == 0 || aliveCharacters.size() == 1)
					return false;
				
				currentChar = aliveCharacters.get(currentCharIndex);
			return true;
			}
		};
		
		DecisionAlgorithm reachAllyForHealALG = new DecisionAlgorithm() {
			
			@Override
			public boolean chooseNode() {
				
				System.out.println("RAGGIUNGO UN ALLY PER CURARLO?");
				if(Character.isInRange(currentChar.getRow(), woundedAlly.getRow(),
										currentChar.getColumn(), woundedAlly.getColumn(),
										 currentChar.getStats().getCharacteristics().getAttackRange()))
					return true;
				return false;
			}
		};
		
		ActionNode moveCloserToAllyAN = new ActionNode() {
			
			@Override
			public void makeAction() {
				
				System.out.println("MI MUOVO VERSO ALLEATO PER CURARLO");
//				try {
//					Thread.sleep(1500);
//				} catch (InterruptedException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
//				
				
				int row, col;
				col = row = (int) currentChar.getStats().getCharacteristics().getMovementRange();
				
				if(currentChar.getRow() - woundedAlly.getRow() > 0)
					row *= -1;
				if(currentChar.getColumn() - woundedAlly.getColumn() > 0)
					col *= -1;
				
				int rowCoord, colCoord;
				
				rowCoord = currentChar.getRow() + row;
				colCoord = currentChar.getColumn() + col;
				
				if(rowCoord < 0)
					rowCoord = 0;
				else if(rowCoord > arena.getSize()-1)
					rowCoord = arena.getSize()-1;
					
				if(colCoord < 0)
					colCoord = 0;
				else if(colCoord > arena.getSize()-1)
					colCoord = arena.getSize()-1;
				
					
					
					ActionMediator actions = player.getActions();
					
					System.out.println("AI: L'HEALER "+currentChar.getName()+" SI MUOVE VERSO WOUNDED");
					actions.clearInformations();
					actions.setAction(MovementAction.GUID);
					actions.addActionInfo(new ActionCoordinateInfo(currentChar.getRow(), currentChar.getColumn()));
					actions.addActionInfo(new ActionCoordinateInfo(rowCoord, colCoord));
				
				
			}
		};
		
		ActionNode switchCharacterAN = new ActionNode() {
			
			@Override
			public void makeAction() {
				
				System.out.println("CAMBIO PERSONAGGIO.");
				
				currentChar = aliveCharacters.get(currentCharIndex);
				
				DecisionNode newRoot = null;
				switch(currentChar.getStats().getName()) {
				
				case "healer":
					newRoot = healerRoot;
//				case "warrior":
//					newRoot = warriorRoot;
//				case "mage":
//					newRoot = mageRoot;
				}
				
				decisionTree = new DecisionTree(newRoot);
				decisionTree.getRoot().makeDecision();
				
			}
		};
		
		ActionNode castHealOnAllyAN = new ActionNode() {
			
			@Override
			public void makeAction() {
				
				System.out.println(currentChar.getName()+" CASTA HEAL SU QUALCUNO");
//				try {
//					Thread.sleep(1500);
//				} catch (InterruptedException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
				
				
				ActionMediator actions = player.getActions();
				
				actions.clearInformations();
				actions.setAction(AbilityAttackAction.GUID);
				actions.addActionInfo(new ActionCoordinateInfo(currentChar.getRow(), currentChar.getColumn()));
				actions.addActionInfo(new ActionSpellInfo(ActionSpellInfo.SECOND_SPELL));
				actions.addActionInfo(new ActionCoordinateInfo(woundedAlly.getRow(), woundedAlly.getColumn()));
				
				
				
			}
		};
		
		ActionNode passTurnAN = new ActionNode() {
			
			@Override
			public void makeAction() {
				
				
//				try {
//					Thread.sleep(1500);
//				} catch (InterruptedException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
				
				ActionMediator actions = player.getActions();
				System.out.println("AI: TERMINA IL TURNO");
				actions.clearInformations();
				actions.setAction(EndTurnAction.GUID);
				
			}
		};
		
		// HEALER DECISION TREE
		healerRoot = new DecisionNode(youHurtALG);
		
		DecisionNode spForHealDN = new DecisionNode(spForHealALG);
		DecisionNode spForHeal2DN = new DecisionNode(spForHealALG);
		
		DecisionNode someoneInjuredDN = new DecisionNode(someoneInjuredALG);
		
		DecisionNode canCastSpellDN = new DecisionNode(canCastSpellALG);
		DecisionNode canCastSpell2DN = new DecisionNode(canCastSpellALG);
		DecisionNode canCastSpell3DN = new DecisionNode(canCastSpellALG);
		
		DecisionNode haveAnotherCharDN = new DecisionNode(haveAnotherCharALG);
		DecisionNode enemyInRangeDN = new DecisionNode(enemyInRangeALG);
		DecisionNode have1SPointDN = new DecisionNode(have1SPointALG);
		
		DecisionNode inRangeForHealDN = new DecisionNode(reachAllyForHealALG);
	
		DecisionNode canYouMoveCloserDN = new DecisionNode(canYouMoveCloserALG);
		
		healerRoot.setYesNode(spForHealDN);
		healerRoot.setNoNode(someoneInjuredDN);
		
		spForHealDN.setYesNode(canCastSpellDN);
		spForHealDN.setNoNode(passTurnAN);
		
		canCastSpellDN.setYesNode(healYourselfAN);
		canCastSpellDN.setNoNode(passTurnAN);
		someoneInjuredDN.setYesNode(spForHeal2DN);
		someoneInjuredDN.setNoNode(enemyInRangeDN);

		enemyInRangeDN.setYesNode(canCastSpell3DN);
		enemyInRangeDN.setNoNode(passTurnAN); //moveeee
		
		canCastSpell3DN.setYesNode(have1SPointDN);
		canCastSpell3DN.setNoNode(haveAnotherCharDN);

		spForHeal2DN.setYesNode(canCastSpell2DN);
		spForHeal2DN.setNoNode(passTurnAN);
		
		have1SPointDN.setYesNode(castHighestEnemySpell);
		have1SPointDN.setNoNode(haveAnotherCharDN);
		
		canCastSpell2DN.setYesNode(castHealOnAllyAN);
		canCastSpell2DN.setNoNode(passTurnAN);
		
//		hasCastedAlreadyDN.setYesNode(passTurnAN);
//		hasCastedAlreadyDN.setNoNode(enemyInRangeDN);
//		
		haveAnotherCharDN.setYesNode(switchCharacterAN);
		haveAnotherCharDN.setNoNode(passTurnAN);
		
//		inRangeForHealDN.setYesNode(passTurnAN);
//		inRangeForHealDN.setNoNode(castHealOnAllyAN);
//		
//		inRangeForHealDN.setYesNode();
//		inRangeForHealDN.setNoNode(passTurnAN);
//		
//		canYouMoveCloserDN.setYesNode(moveCloserToAllyAN);
//		
//		//!TODO
//		canYouMoveCloserDN.setNoNode(haveAnotherCharDN);
//		
//		hasCastedAlready2DN.setYesNode(passTurnAN);
//		hasCastedAlready2DN.setNoNode(castHealOnAllyAN);
		
	}



	@Override
	public void makeMove() 
	{
		decisionTree.getRoot().makeDecision();
	}



	@Override
	public void preStartMatch(Map map) {
		super.preStartMatch(map);
			int averageLevel = 0;
			List<Character> enemyCharacters = enemyPlayer.getCharacters();
			
			for (Character character : enemyCharacters) 
			{
				averageLevel += character.getLevel();
			}

			int size = enemyCharacters.size() != 0 ? enemyCharacters.size() : 1;
			averageLevel /= size;

			
			Character aiCharacter = null;
			for(int index = 0; index < 2;index++)
			{
				aiCharacter = new Character(averageLevel, 0, player);
				aiCharacter.setStats(factory.levellingRole("healer", averageLevel));
				player.addCharacter(aiCharacter);
				aiCharacter.setName("aibot"+index);
			}
			
			arena = map.getArena();
			RBTree<SpawnCoordinate> spawns = arena.getSpawnCells();
			
			ActionMediator aiActionMediator = player.getActions();
			int index = 0;
			Iterator<SpawnCoordinate> spawnPoints = spawns.iterator();
			SpawnCoordinate spawnCoordinate = null;
			boolean cicle = true;
	        for (Character aiCharacter2 : player.getCharacters())
	        {
	                aiActionMediator.setAction(StartingPointAction.GUID);
	                while(spawnPoints.hasNext() && cicle)
	                {
	                        spawnCoordinate = spawnPoints.next();
	                        if(arena.isFree(spawnCoordinate.getIRow(), spawnCoordinate.getIColumn()))
	                        {
	                                index++;
//	                              System.out.println("aggiungo");
	                                AddCharacterInfo aci = new AddCharacterInfo();
	                                aci.setCharacter(aiCharacter2);
	                               
	                                aiActionMediator.addActionInfo(aci);
	                                aiActionMediator.addActionInfo(new ActionCoordinateInfo(spawnCoordinate.getIRow(), spawnCoordinate.getIColumn()));
	                                System.out.println("AGGIUNGO "+aiActionMediator.playAction()+" "+spawnCoordinate.getIRow()+" "+spawnCoordinate.getIColumn());
	                                cicle = false;
	                        }
	                }
	                cicle = true;
	        }
		
			aliveCharacters = new LinkedList<Character>();
			for(Character character : player.getCharacters())
				aliveCharacters.add(character);
			
			currentChar = aliveCharacters.get(currentCharIndex);
			
			DecisionNode root = null;
			switch(currentChar.getStats().getName()) {
			
			case "healer":
				root = healerRoot;
//			case "warrior":
//				newRoot = warriorRoot;
//			case "mage":
//				newRoot = mageRoot;
			}
			
			decisionTree = new DecisionTree(root);
			
	}



	@Override
	public void update() {
		
		for(int i = 0; i < aliveCharacters.size(); i++)
			if(!aliveCharacters.get(i).isAlive())
				aliveCharacters.remove(i);
		
		currentCharIndex = 0;
		currentChar = aliveCharacters.get(currentCharIndex);
		
		
	}



	@Override
	public void aiWait() {
		// TODO Auto-generated method stub
		
	}

}
